using Engine;
using Engine.Graphics.VertexStructures;
using Engine.Shape;
using SharpDX;
using SharpDX.Direct3D11;
using System.Collections.Generic;
using System.Diagnostics;

namespace Scenario_Editor.Tools
{
    /// <summary>
    /// Class which stores the updated geometrie for terrain cells which are slied by roads or houses etc.
    /// </summary>
    internal class Cell
    {
        private System.Drawing.Point coordinate;
        private Device device;

        /// <summary>
        /// the Indices after Triangulation
        /// </summary>
        private List<short> indices;

        private bool isCuttedWithRoad;
        private ushort[] materials;
        private Vector3[] normalForEachPolygon;
        private Vector3[] normalForOriginalPolygons;
        private short[] numberOfPolygonsForMaterial;

        /// <summary>
        /// The Indices of the cell when no shape is cutting it.
        /// </summary>
        private List<short> originalIndices;

        /// <summary>
        /// The vertices of the cell when no shape is cutting it.
        /// </summary>
        private MyOwnVertex.MyPositionNormalTextured[] originalVertexe;

        /// <summary>
        /// This is a List of Shapes which are used to cut the cell. The value-List of the dictionary are the used borderIds.
        /// </summary>
        public Dictionary<Shapes, List<int>> registeredShapes;

        private bool rotated90Degree;

        /// <summary>
        /// buffer of polygon, which also uses this position.
        /// </summary>
        private Dictionary<Vector3, PolygonData> sharingPolygons;

        private System.Drawing.Point[] vertexCoordinates;

        /// <summary>
        /// The Vertexe after Triangulation
        /// </summary>
        private List<MyOwnVertex.MyPositionNormalTextured> vertexe;

        internal Cell(Device device, int x, int y, MyOwnVertex.MyPositionNormalTextured[,] terrainVertices, ushort material1, ushort material2, bool rotated90Degree)
        {
            this.device = device;
            this.coordinate = new System.Drawing.Point(x, y);
            this.rotated90Degree = rotated90Degree;
            this.vertexCoordinates = new System.Drawing.Point[4];
            this.vertexCoordinates[0] = new System.Drawing.Point(x, y);
            this.vertexCoordinates[1] = new System.Drawing.Point(x, y + 1);
            this.vertexCoordinates[2] = new System.Drawing.Point(x + 1, y);
            this.vertexCoordinates[3] = new System.Drawing.Point(x + 1, y + 1);
            this.normalForOriginalPolygons = new Vector3[2];
            this.normalForEachPolygon = new Vector3[2];

            this.registeredShapes = new Dictionary<Shapes, List<int>>();
            this.indices = new List<short>();
            this.vertexe = new List<MyOwnVertex.MyPositionNormalTextured>();
            this.sharingPolygons = new Dictionary<Vector3, PolygonData>();
            this.numberOfPolygonsForMaterial = new short[] { 1, 1 };
            this.originalIndices = new List<short>();

            if (this.rotated90Degree)
            {
                this.originalIndices.Add(0);
                this.originalIndices.Add(1);
                this.originalIndices.Add(2);
                this.originalIndices.Add(2);
                this.originalIndices.Add(1);
                this.originalIndices.Add(3);
            }
            else
            {
                this.originalIndices.Add(0);
                this.originalIndices.Add(1);
                this.originalIndices.Add(3);
                this.originalIndices.Add(3);
                this.originalIndices.Add(2);
                this.originalIndices.Add(0);
            }

            this.originalVertexe = new MyOwnVertex.MyPositionNormalTextured[4];
            this.originalVertexe[0] = (terrainVertices[this.vertexCoordinates[0].X, this.vertexCoordinates[0].Y]);
            this.originalVertexe[1] = (terrainVertices[this.vertexCoordinates[1].X, this.vertexCoordinates[1].Y]);
            this.originalVertexe[2] = (terrainVertices[this.vertexCoordinates[2].X, this.vertexCoordinates[2].Y]);
            this.originalVertexe[3] = (terrainVertices[this.vertexCoordinates[3].X, this.vertexCoordinates[3].Y]);

            // Update normals for Original Polygons;
            UpdateNormalForOriginalPolygons();

            this.indices.AddRange(this.originalIndices);
            this.vertexe.AddRange(this.originalVertexe);

            this.materials = new ushort[] { (ushort)material1, (ushort)material2 };
            this.isCuttedWithRoad = false;
        }

        ~Cell()
        {
            this.sharingPolygons.Clear();
            this.sharingPolygons = null;
            this.vertexe.Clear();
            this.vertexe = null;
            this.registeredShapes.Clear();
            this.registeredShapes = null;
            this.originalIndices.Clear();
            this.originalIndices = null;
            this.indices.Clear();
            this.indices = null;
            this.device = null;
            this.materials = null;
            this.normalForEachPolygon = null;
            this.normalForOriginalPolygons = null;
            this.numberOfPolygonsForMaterial = null;
            this.originalVertexe = null;
            this.vertexCoordinates = null;
        }

        public bool ContainsStreet
        {
            get
            {
                return isCuttedWithRoad;
            }
            set
            {
                isCuttedWithRoad = value;
            }
        }

        internal List<short> Indices
        {
            get
            {
                return this.indices;
            }
        }

        internal ushort[] Material
        {
            get
            {
                return this.materials;
            }
            set
            {
                this.materials = value;
            }
        }

        public Vector3[] NormalForEachPolygon
        {
            get { return normalForEachPolygon; }
            set { normalForEachPolygon = value; }
        }

        public short[] NumberOfPolygonsForMaterial
        {
            get { return numberOfPolygonsForMaterial; }
            set { numberOfPolygonsForMaterial = value; }
        }

        internal List<short> OriginalIndices
        {
            get
            {
                return this.originalIndices;
            }
        }

        internal MyOwnVertex.MyPositionNormalTextured[] OriginalVertex
        {
            get
            {
                return this.originalVertexe;
            }
        }

        public bool Rotated90Degree
        {
            get { return rotated90Degree; }
            set { rotated90Degree = value; }
        }

        internal System.Drawing.Point Vertex1
        {
            get
            {
                return this.vertexCoordinates[0];
            }
        }

        internal System.Drawing.Point Vertex2
        {
            get
            {
                return this.vertexCoordinates[1];
            }
        }

        internal System.Drawing.Point Vertex3
        {
            get
            {
                return this.vertexCoordinates[2];
            }
        }

        internal System.Drawing.Point Vertex4
        {
            get
            {
                return this.vertexCoordinates[3];
            }
        }

        internal System.Drawing.Point[] VertexCoordinates
        {
            get
            {
                return this.vertexCoordinates;
            }
        }

        internal List<MyOwnVertex.MyPositionNormalTextured> Vertices
        {
            get
            {
                return this.vertexe;
            }
        }

        public void RegisterShape(Shapes shape, List<int> borderIds)
        {
            lock (this.registeredShapes)
            {
                if (!this.registeredShapes.ContainsKey(shape))
                {
                    this.registeredShapes.Add(shape, borderIds);
                }
                else
                {
                    this.registeredShapes.Remove(shape);
                    this.registeredShapes.Add(shape, borderIds);
                }
            }
        }

        public void UnregisterShape(Shapes shape)
        {
            lock (this.registeredShapes)
            {
                while (this.registeredShapes.ContainsKey(shape))
                {
                    this.registeredShapes.Remove(shape);
                }
            }
        }

        /// <summary>
        /// Get Intersection of the original two triangles.
        /// </summary>
        /// <param name="ray"></param>
        /// <param name="positiveSide"></param>
        /// <param name="negativeSide"></param>
        /// <param name="distance"></param>
        /// <returns></returns>
        internal bool GetIntersection(Ray ray, bool positiveSide, bool negativeSide, out float distance)
        {
            int polygonId;
            return GetIntersection(ray, positiveSide, negativeSide, out distance, out polygonId);
        }

        /// <summary>
        /// Get Intersection of the original two triangles.
        /// </summary>
        /// <param name="ray"></param>
        /// <param name="positiveSide"></param>
        /// <param name="negativeSide"></param>
        /// <param name="distance"></param>
        /// <param name="polygonId"></param>
        /// <returns></returns>
        internal bool GetIntersection(Ray ray, bool positiveSide, bool negativeSide, out float distance, out int polygonId)
        {
            distance = 0.0f;

            Vector3 normal = this.normalForOriginalPolygons[0];

            bool intersect = GeoMath.IntersectsTriangle(ray,
                this.originalVertexe[this.originalIndices[0 * 3]].Position,
                this.originalVertexe[this.originalIndices[0 * 3 + 1]].Position,
                this.originalVertexe[this.originalIndices[0 * 3 + 2]].Position,
                normal,
                 true, false, out distance);

            if (intersect)
            {
                polygonId = 0;
                return true;
            }
            else
            {
                normal = this.normalForOriginalPolygons[1];

                intersect = GeoMath.IntersectsTriangle(ray,
                    this.originalVertexe[this.originalIndices[1 * 3]].Position,
                    this.originalVertexe[this.originalIndices[1 * 3 + 1]].Position,
                    this.originalVertexe[this.originalIndices[1 * 3 + 2]].Position,
                    normal,
                     true, false, out distance);

                if (intersect)
                {
                    polygonId = 1;
                    return true;
                }
            }
            polygonId = -1;
            return false;
        }

        internal bool GetIntersectionOfCompleteMesh(Ray ray, out float shortestDistance)
        {
            shortestDistance = float.MaxValue;
            float distance = float.MaxValue;

            Vector3 normal = this.normalForOriginalPolygons[0];
            lock (this)
                for (int i = 0; i < this.indices.Count / 3; i++)
                {
                    int vertexID1 = this.indices[i * 3];
                    int vertexID2 = this.indices[i * 3 + 1];
                    int vertexID3 = this.indices[i * 3 + 2];

                    bool intersect = GeoMath.IntersectsTriangle(ray,
                        this.vertexe[vertexID1].Position,
                        this.vertexe[vertexID2].Position,
                        this.vertexe[vertexID3].Position,
                        normal,
                         true, false, out distance);

                    if (intersect)
                    {
                        shortestDistance = (distance < shortestDistance) ? distance : shortestDistance;
                    }
                }

            if (shortestDistance < float.MaxValue)
                return true;
            shortestDistance = float.MaxValue;
            return false;
        }

        internal int GetMateialFromPolygon(Ray ray, bool positiveSide, bool negativeSide)
        {
            int materialID = -1;
            float distance = 0.0f;
            for (int i = 0; i < this.originalIndices.Count / 3; i++)
            {
                Vector3 normal = GeoMath.Normal2Poly(
                                this.originalVertexe[this.originalIndices[i * 3]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 1]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 2]].Position
                                );

                bool intersect = GeoMath.IntersectsTriangle(ray,
                                this.originalVertexe[this.originalIndices[i * 3]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 1]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 2]].Position,
                                normal,
                                positiveSide, negativeSide, out distance);
                if (intersect)
                {
                    materialID = this.materials[i];
                }
            }
            return materialID;
        }

        internal Vector3 GetNormalFromOriginalPolygon(int polygonId)
        {
            return GeoMath.Normal2Poly(this.OriginalVertex[this.OriginalIndices[polygonId * 3]].Position,
                 this.OriginalVertex[this.OriginalIndices[polygonId * 3 + 1]].Position,
                 this.OriginalVertex[this.OriginalIndices[polygonId * 3 + 2]].Position);
        }

        internal Vector3 GetNormalFromPosition(Vector3 position)
        {
            PolygonData polyData;

            if (this.sharingPolygons.TryGetValue(position, out polyData))
            {
                return polyData.Normal;
            }
            return Vector3.Zero;
        }

        internal bool GetPolygonIdOfIntersection(Ray ray, bool positiveSide, bool negativeSide, out bool firstMaterialSlot)
        {
            bool intersect = false;
            firstMaterialSlot = true;
            float distance = 0.0f;

            for (int i = 0; i < this.originalIndices.Count / 3; i++)
            {
                Vector3 normal = GeoMath.Normal2Poly(
                                this.originalVertexe[this.originalIndices[i * 3]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 1]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 2]].Position
                                );

                bool intersectPolygon = GeoMath.IntersectsTriangle(ray,
                                this.originalVertexe[this.originalIndices[i * 3]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 1]].Position,
                                this.originalVertexe[this.originalIndices[i * 3 + 2]].Position,
                                normal,
                                true, false, out distance);

                if (intersectPolygon)
                {
                    if (i > 0)
                        firstMaterialSlot = false;
                    intersect = true;
                }
            }

            return intersect;
        }

        internal void SetNormalForPosition(Vector3 position, Vector3 normal)
        {
            PolygonData polyData;
            if (this.sharingPolygons.TryGetValue(position, out polyData))
            {
                if (polyData != null)
                {
                    MyOwnVertex.MyPositionNormalTextured vertex = this.vertexe[polyData.VertexID];
                    vertex.Normal = normal;
                    this.vertexe[polyData.VertexID] = vertex;
                }
            }
        }

        internal void UpdateNormalForOriginalPolygons()
        {
            for (int i = 0; i < this.originalIndices.Count / 3; i++)
            {
                this.normalForOriginalPolygons[i] = GeoMath.Normal2Poly(this.originalVertexe[this.originalIndices[i * 3]].Position,
                    this.originalVertexe[this.originalIndices[i * 3 + 1]].Position,
                    this.originalVertexe[this.originalIndices[i * 3 + 2]].Position);
            }
        }

        internal void UpdateShape(MyOwnVertex.MyPositionNormalTextured[,] vertices, Device device)
        {
            lock (this)
            {
                List<MyOwnVertex.MyPositionNormalTextured> triangulatedShapeVertices = new List<MyOwnVertex.MyPositionNormalTextured>();
                Shapes completeShape = new Shapes(device);
                this.ContainsStreet = false;
                this.NumberOfPolygonsForMaterial = new short[2];
                this.normalForEachPolygon = new Vector3[this.NumberOfPolygonsForMaterial.Length];
                this.vertexe.Clear();
                this.indices.Clear();

                for (int indices = 0; indices < NumberOfPolygonsForMaterial.Length; indices++)
                {
                    if (this.materials[indices] != 0)
                    {
                        Shapes.EnableCrosspointDebugging = false;
                    }
                    else
                    {
                        Shapes.EnableCrosspointDebugging = false;
                    }

                    Shapes tempShape = new Shapes(device);
                    List<Vector3> vectors = new List<Vector3>(4);

                    // Add the source vertices from terrain.
                    vectors.Add(this.originalVertexe[this.originalIndices[indices * 3]].Position);
                    vectors.Add(this.originalVertexe[this.originalIndices[indices * 3 + 2]].Position);
                    vectors.Add(this.originalVertexe[this.originalIndices[indices * 3 + 1]].Position);
                    vectors.Add(this.originalVertexe[this.originalIndices[indices * 3]].Position);
                    UpdateNormalForOriginalPolygons();

                    tempShape.AddBorder(new Border(vectors));

                    // cast the roads
                    lock (this.registeredShapes)
                    {
                        foreach (var shape in this.registeredShapes.Keys)
                        {
                            lock (shape.thisLock)
                            {
                                //try
                                //{
                                bool shapeChanged = false;
                                List<int> borderIds;
                                if (this.registeredShapes.TryGetValue(shape, out borderIds))
                                    foreach (var id in borderIds)
                                    {
                                        Shapes temp = new Shapes(this.device);
                                        if (id >= shape.GetNumberOfTransformedBorders())
                                            continue;
                                        temp.AddBorder(shape.GetBorder(id));

                                        tempShape = Shapes.CastShapes(tempShape, temp, device, out shapeChanged);

                                        if (shapeChanged)
                                        {
                                            this.ContainsStreet = true;
                                        }
                                    }
                                //}
                                //catch (Exception e)
                                //{
                                //    Console.WriteLine(e.ToString());
                                //}
                            }
                        }
                    }

                    int numberOfPolygonsForBorder = 0;

                    // Add the temp mesh to the complete Shape.
                    for (int i = 0; i < tempShape.GetBorders().Length; i++)
                    {
                        Border border = tempShape.GetBorder(i);

                        if (border.GetNumberOfPositions() == 4 && GeoMath.GetSurfaceAreaFromTriangle(border.GetPositionAt(0),
                                      border.GetPositionAt(1),
                                      border.GetPositionAt(2)) < 0.10f)
                        {
                            triangulatedShapeVertices.Clear();
                            border.ClearPositions();
                        }

                        int positions = border.GetNumberOfPositions();
                        // Check if it the border could be a valid polygon.
                        if (positions > 3)
                        {
                            numberOfPolygonsForBorder += positions - 3;
                            completeShape.AddBorder(tempShape.GetBorder(i));
                        }
                        else
                        {
                            //Shapes.DrawLinesFromPoints(vectors, device, true, 0xFFFF00, 50, 0, false);
                        }
                    }

                    this.NumberOfPolygonsForMaterial[indices] = (short)numberOfPolygonsForBorder;
                }

                // After cutting the shape it must triangulated.
                try
                {
                    Triangulate.TriangulateShape(out triangulatedShapeVertices, completeShape);
                }
                catch
                {
                    Debug.WriteLine("Error at triangulation!! circa at World Coordinate: " + this.OriginalVertex[0].Position.ToString());//+ System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock.Time.Normal.ToString());
#if DEBUG
                    // Saves the Shape to a text-file. The code can be insert in a Method
                    // and can be used to find the error in triangulation
                    completeShape.SaveToTextFile("ErrorWithTriangulate");
#endif
                }

                // Create the new Indices and Vertex - Use the VertexColors from the nearest Corner Vertex
                short numberOfPolygons = (short)(triangulatedShapeVertices.Count / 3);
                this.normalForEachPolygon = new Vector3[numberOfPolygons];
                this.sharingPolygons.Clear();
                this.vertexe.Clear();
                this.indices.Clear();

                for (short i = 0; i < numberOfPolygons; i++)
                {
                    Vector3[] polygonPositions = new Vector3[3];
                    for (int p = 0; p < 3; p++)
                    {
                        polygonPositions[p] = triangulatedShapeVertices[i * 3 + p].Position;
                        if (!this.sharingPolygons.ContainsKey(polygonPositions[p]))
                        {
                            this.sharingPolygons.Add(polygonPositions[p], new PolygonData((short)this.vertexe.Count, i));
                            this.indices.Add((short)this.vertexe.Count);
                            this.vertexe.Add(triangulatedShapeVertices[i * 3 + p]);
                        }
                        else
                        {
                            PolygonData polygonData;
                            if (this.sharingPolygons.TryGetValue(polygonPositions[p], out polygonData))
                            {
                                if (!polygonData.PolygonIds.Contains(i))
                                    polygonData.PolygonIds.Add(i);

                                this.indices.Add((short)polygonData.VertexID);
                            }
                        }
                    }

                    // Create the normal from last polygon.
                    this.normalForEachPolygon[i] = GeoMath.Normal2Poly(polygonPositions[0], polygonPositions[1], polygonPositions[2]);
                }

                // Precalculate the normal from all polys and set it to the polydata.
                for (int i = 0; i < this.vertexe.Count; i++)
                {
                    PolygonData polydata;
                    if (this.sharingPolygons.TryGetValue(this.vertexe[i].Position, out polydata))
                    {
                        Vector3 normal = Vector3.Zero;
                        for (int p = 0; p < polydata.PolygonIds.Count; p++)
                        {
                            normal += this.normalForEachPolygon[polydata.PolygonIds[p]];
                        }
                        normal.Normalize();
                        polydata.Normal = normal;
                    }
                }

                // Set the vertex Color of all vertices.
                for (int i = 0; i < this.vertexe.Count; i++)
                {
                    MyOwnVertex.MyPositionNormalTextured vertex = this.vertexe[i];

                    float distance = GeoMath.DistancePlanar(vertex.Position, this.OriginalVertex[0].Position);
                    float smallestDistance = distance;
                    vertex.VertexColor = this.OriginalVertex[0].VertexColor;
                    vertex.DiffuseColor = this.OriginalVertex[0].DiffuseColor;
                    vertex.Tu = vertex.Position.X / 10.0f;
                    vertex.Tv = vertex.Position.Z / 10.0f;
                    vertex.Tu2 = vertex.Position.X / 100.0f;
                    vertex.Tv2 = vertex.Position.Z / 100.0f;

                    distance = GeoMath.DistancePlanar(vertex.Position, this.OriginalVertex[1].Position);
                    if (distance < smallestDistance)
                    {
                        smallestDistance = distance;
                        vertex.VertexColor = this.OriginalVertex[1].VertexColor;
                        vertex.DiffuseColor = this.OriginalVertex[1].DiffuseColor;
                    }

                    distance = GeoMath.DistancePlanar(vertex.Position, this.OriginalVertex[2].Position);
                    if (distance < smallestDistance)
                    {
                        smallestDistance = distance;
                        vertex.VertexColor = this.OriginalVertex[2].VertexColor;
                        vertex.DiffuseColor = this.OriginalVertex[2].DiffuseColor;
                    }

                    distance = GeoMath.DistancePlanar(vertex.Position, this.OriginalVertex[3].Position);
                    if (distance < smallestDistance)
                    {
                        smallestDistance = distance;
                        vertex.VertexColor = this.OriginalVertex[3].VertexColor;
                        vertex.DiffuseColor = this.OriginalVertex[3].DiffuseColor;
                    }

                    this.vertexe[i] = vertex;
                }
            }
        }
    }
}